package com.katze.boot.plugins.security.config;

import com.katze.boot.plugins.redis.service.CacheService;
import com.katze.boot.plugins.security.domain.UserDetail;
import com.katze.boot.plugins.security.filter.DownloadProgressFilter;
import com.katze.boot.plugins.security.filter.JwtAuthenticationFilter;
import com.katze.boot.plugins.security.filter.SpringDocFilter;
import com.katze.boot.plugins.security.handle.AuthenticationEntryPointImpl;
import com.katze.boot.plugins.security.handle.AuthorizeExpressionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity    // 开启权限注解，如：@PreAuthorize注解
public class WebSecurityConfig {

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Bean
    public SpringDocFilter springDocFilter() {
        return new SpringDocFilter();
    }

    @Bean
    public DownloadProgressFilter downloadProgressFilter() {
        return new DownloadProgressFilter();
    }

    /**
     * 定义安全请求拦截规则
     *
     * @anyRequest |   匹配所有请求路径
     * @access |   SpringEl表达式结果为true时可以访问
     * @anonymous |   匿名可以访问
     * @denyAll |   用户不能访问
     * @fullyAuthenticated |   用户完全认证可以访问（非remember-me下自动登录）
     * @hasAnyAuthority |   如果有参数，参数表示权限，则其中任何一个权限可以访问
     * @hasAnyRole |   如果有参数，参数表示角色，则其中任何一个角色可以访问
     * @hasAuthority |   如果有参数，参数表示权限，则其权限可以访问
     * @hasIpAddress |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
     * @hasRole |   如果有参数，参数表示角色，则其角色可以访问
     * @permitAll |   用户可以任意访问
     * @rememberMe |   允许通过remember-me登录的用户访问
     * @authenticated |   用户登录后可访问
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity security,
                                                   JwtAuthenticationFilter jwtAuthenticationFilter,
                                                   SpringDocFilter springDocFilter,
                                                   DownloadProgressFilter downloadProgressFilter,
                                                   AccessDeniedHandler accessDeniedHandler) throws Exception {
        security.authorizeHttpRequests(authorize -> {
                    authorize.requestMatchers(HttpMethod.OPTIONS).permitAll()
                            // 接口放行，不进行权限校验
                            .requestMatchers("/actuator/**").permitAll()
                            .requestMatchers(springDocFilter.getPatterns()).permitAll()
                            .requestMatchers(downloadProgressFilter.getPatterns()).permitAll()
                            .requestMatchers(jwtAuthenticationFilter.getPatterns()).permitAll()
                            // 所有请求都需要认证
                            .anyRequest().authenticated();
                })
                // csrf禁用，前后端分离不使用session
                .csrf(AbstractHttpConfigurer::disable)
                // session禁用, 前后端分离架构使用token
                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 禁用缓存
                .headers(headers -> headers.cacheControl(HeadersConfigurer.CacheControlConfig::disable).frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                // 访问异常处理
                .exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(accessDeniedHandler))
                // 未授权异常处理
                .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(new AuthenticationEntryPointImpl()));

                // 访问springdoc open-api 地址是需要携带token
                // todo 可以根据配置是否启用过滤器
        security.addFilterBefore(springDocFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(downloadProgressFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加入口filter， 前后端分离的时候，可以进行token解析操作
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return security.build();
    }

    /**
     * 明文密码加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(CacheService cacheService, PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder);
        //将编写的UserDetailsService注入进来
        provider.setUserDetailsService(username -> cacheService.loadUserByUsername(username, UserDetail.class));
        //将provider放置到AuthenticationManager 中
        return new ProviderManager(provider);
    }

    /**
     * 自定义权限注解校验, PrePostMethodSecurityConfiguration.setExpressionHandler方法接收AuthorizeExpressionHandler
     * @param context 上下文
     */
    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler(ApplicationContext context) {
        AuthorizeExpressionHandler expressionHandler = new AuthorizeExpressionHandler();
        expressionHandler.setApplicationContext(context);
        return expressionHandler;
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        System.out.println(encoder.encode("1234qwer"));
    }
}
